﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;

namespace Gp.Scripts.Character {
       public static class Utils {
        /// <summary>
        /// Warning message if a input was not found when trying to retrieve it
        /// </summary>
        private const string InputNotDefinedWarning = "An input which is used by one or more scripts is not defined: ";

        /// <summary>
        /// Enum for the different phases an input can have in Unity. Refer https://docs.unity3d.com/ScriptReference/Input.html for more information
        /// </summary>
        public enum InputPhase {
            Pressed,
            Down,
            Up,
            Smoothed,
            Raw
        }
        

        /// <summary>
        /// A custom modulo operation for calculating mod of a negative number as well
        /// </summary>
        /// <param name="dividend">Dividend</param>
        /// <param name="divisor">Divisor</param>
        /// <returns>The modulo of the given dividend divided by the given divisor</returns>
        public static float CustomModulo(float dividend, float divisor) {
            if (dividend < 0) {
                return dividend - divisor * Mathf.Ceil(dividend / divisor);
            } else {
                return dividend - divisor * Mathf.Floor(dividend / divisor);
            }
        }

        /// <summary>
        /// Checks if two floats are considered equal according to the given epsilon. Returns true if the distance between a and b is smaller than epsilon
        /// </summary>
        /// <param name="a">Left-side float</param>
        /// <param name="b">Right-side float</param>
        /// <param name="epsilon">Minimum value for inequality</param>
        /// <returns>True if the distance between a and b is smaller than epsilon</returns>
        public static bool IsAlmostEqual(float a, float b, float epsilon) {
            return Mathf.Abs(a - b) < epsilon;
        }

        /// <summary>
        /// Checks if the given vectors are almost equal, i.e. their angle is smaller than 1 degree
        /// </summary>
        /// <param name="a">Left-side vector</param>
        /// <param name="b">Right-side vector</param>
        /// <returns>True if the given vectors are almost equal</returns>
        public static bool IsAlmostEqual(Vector3 a, Vector3 b) {
            return Vector3.Angle(a, b) < 1.0f;
        }

        /// <summary>
        /// Returns the signed angle between vector a and b on the plane with normal normal. The result range is in (-180, 180]
        /// </summary>
        /// <param name="a">First vector</param>
        /// <param name="b">Second vector</param>
        /// <param name="normal">Plane normal for projecting vector a and b</param>
        /// <returns>Signed angle on plane with normal normal</returns>
        public static float SignedAngle(Vector3 a, Vector3 b, Vector3 normal) {
            // Project a and b onto the plane with normal normal
            a = Vector3.ProjectOnPlane(a, normal);
            b = Vector3.ProjectOnPlane(b, normal);
            // Calculate the signed angle between them
            return Vector3.SignedAngle(a, b, normal);
        }

        /// <summary>
        /// Checks if the given layer is part of the given layer mask
        /// </summary>
        /// <param name="layer">Layer to check</param>
        /// <param name="layerMask">Layer mask to look in for layer</param>
        /// <returns>True if layer is in layerMask, otherwise false</returns>
        public static bool LayerInLayerMask(int layer, LayerMask layerMask) {
            return ((layerMask.value & (1 << layer)) > 0);
        }

        /// <summary>
        /// Enables the ZWrite property of each shader assigned to materials of renderer r
        /// </summary>
        /// <param name="r">Renderer whose material shader ZWrite property should change</param>
        public static void EnableZWrite(Renderer r) {
            foreach (Material m in r.materials) {
                if (m.HasProperty("_Color")) {
                    m.SetInt("_ZWrite", 1);
                    m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                }
            }
        }

        /// <summary>
        /// Disables the ZWrite property of each shader assigned to materials of renderer r
        /// </summary>
        /// <param name="r">Renderer whose material shader ZWrite property should change</param>
        public static void DisableZWrite(Renderer r) {
            foreach (Material m in r.materials) {
                if (m.HasProperty("_Color")) {
                    m.SetInt("_ZWrite", 0);
                    m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent + 100;
                }
            }
        }

        /// <summary>
		/// Checks if the pointer is over a GUI element
		/// </summary>
		/// <returns>True if the pointer is over a GUI element, otherwise false</returns>
        public static bool IsPointerOverGUI() {
            if (!EventSystem.current) {
                return false;
            }
            PointerEventData eventData = new PointerEventData(EventSystem.current);
            eventData.position = Mouse.current.position.ReadValue();
            List<RaycastResult> raycastResults = new List<RaycastResult>();
            EventSystem.current.RaycastAll(eventData, raycastResults);
            return raycastResults.Count > 0;
        }

        /// <summary>
        /// Generate a random integer in the interval [min, max]
        /// </summary>
        /// <param name="min">Inclusive lower bound</param>
        /// <param name="max">Inclusive upper bound</param>
        /// <returns>A random integer from the interval [min, max]</returns>
        public static int RandomInteger(int min, int max) {
            return (new System.Random()).Next(min, max + 1);
        }
    }
}